package cn.tqfeiyang.springplus.generator.jdbc;

import cn.tqfeiyang.springplus.generator.config.GeneratorConfig;
import cn.tqfeiyang.springplus.generator.name.NameConverter;
import cn.tqfeiyang.springplus.generator.name.NameFormat;
import cn.tqfeiyang.springplus.generator.name.NamingHelper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import lombok.Getter;
import lombok.Setter;

import java.util.*;

/**
 * 表信息
 */
@Setter
@Getter
public class TableInfo {

    /**
     * 生成器配置
     */
    private GeneratorConfig generatorConfig;

    /**
     * 数据库模式
     */
    private String schema;

    /**
     * 数据库目录
     */
    private String catalog;

    /**
     * 表名称
     */
    private String name;

    /**
     * 表注释
     */
    private String comment;

    /**
     * 模块名称
     */
    private String moduleName;

    /**
     * 实体名称
     */
    private String entityName;

    /**
     * 领域名称
     */
    private String domainName;

    /**
     * Apifox 目录名
     */
    private String apifoxFolder = "";

    /**
     * 是否支持映射？
     */
    private boolean supportMapper = true;

    /**
     * 是否支持服务？
     */
    private boolean supportService = true;

    /**
     * 是否支持控制器？
     */
    private boolean supportController = true;

    /**
     * 支持缓存？
     */
    private boolean supportCache = false;

    /**
     * 一对多关联
     */
    private List<JoinInfo.OneToManyJoin> oneToManyJoins = new ArrayList<>();

    /**
     * 是否有一对多关联？
     */
    public boolean getHasOneToManyJoins() {
        return CollectionUtils.isNotEmpty(getOneToManyJoins());
    }

    /**
     * 多对多关联
     */
    private List<JoinInfo.ManyToManyJoin> manyToManyJoins = new ArrayList<>();

    /**
     * 是否有多对多关联？
     */
    public boolean getHasManyToManyJoins() {
        return CollectionUtils.isNotEmpty(getManyToManyJoins());
    }

    /**
     * 多对一关联
     */
    public List<JoinInfo.ManyToOneJoin> getManyToOneJoins() {
        List<JoinInfo.ManyToOneJoin> manyToOneJoins = new ArrayList<>();
        fields.stream().forEach(fieldInfo -> {
            if (fieldInfo.getManyToOneJoin() != null) {
                manyToOneJoins.add(fieldInfo.getManyToOneJoin());
            }
        });
        return manyToOneJoins;
    }

    /**
     * 是否有多对一关联？
     */
    public boolean getHasManyToOneJoins() {
        return CollectionUtils.isNotEmpty(getManyToOneJoins());
    }

    /**
     * 获取所有的关联？
     */
    public List<JoinInfo> getJoins() {
        List<JoinInfo> joins = new ArrayList<>();
        joins.addAll(getOneToManyJoins());
        joins.addAll(getManyToOneJoins());
        joins.addAll(getManyToManyJoins());
        return joins;
    }

    /**
     * 是否有关联？
     */
    public boolean getHasJoins() {
        return CollectionUtils.isNotEmpty(getJoins());
    }

    /**
     * 获取关联实体导入包
     */
    public List<String> getJoinEntityImportPackages() {
        Set<String> importPackages = new HashSet<>();
        NameConverter nameConverter = generatorConfig.getNameConverter();
        getOneToManyJoins().stream().forEach(join -> importPackages.add(nameConverter.convert(join.getJoinTable(), NameFormat.EntityImport)));
        getManyToOneJoins().stream().forEach(join -> importPackages.add(nameConverter.convert(join.getJoinTable(), NameFormat.EntityImport)));
        getManyToManyJoins().stream().forEach(join -> {
            importPackages.add(nameConverter.convert(join.getJoinTable(), NameFormat.EntityImport));
            importPackages.add(nameConverter.convert(join.getCrossTable(), NameFormat.EntityImport));
        });
        if (getHasOneToManyJoins() || getHasManyToManyJoins()) {
            importPackages.add("java.util.List");
        }
        return importPackages.stream().sorted().toList();
    }

    /**
     * 表字段
     */
    private final List<FieldInfo> fields = new ArrayList<>();

    /**
     * 公共字段
     */
    private final List<FieldInfo> commonFields = new ArrayList<>();

    /**
     * 是否有通用字段？
     */
    public boolean getHasCommonFields() {
        return CollectionUtils.isNotEmpty(getCommonFields());
    }

    /**
     * 获取导入包列表
     */
    public Set<String> getImportPackages() {
        Set<String> importPackages = new HashSet<>();
        fields.stream().forEach(fieldInfo -> {
            if (StringUtils.isNotBlank(fieldInfo.getColumnType().getPkg())) {
                importPackages.add(fieldInfo.getColumnType().getPkg());
            }
        });
        return importPackages;
    }

    /**
     * 表类型，TABLE或VIEW
     */
    private String tableType;

    /**
     * 是否是视图？
     *
     * @return
     */
    public boolean isView() {
        return "VIEW".equals(tableType);
    }

    /**
     * 如果表类型是CROSS。该对象反应的是交叉信息
     */
    private CrossInfo crossInfo;

    /**
     * 获取主键字段
     */
    public FieldInfo getKeyField() {
        return fields.stream().filter(fieldInfo -> fieldInfo.isPrimaryKey())
                .findFirst().orElse(null);
    }

    /**
     * 是否有主键字段？
     */
    public boolean getHasKeyField() {
        return Objects.nonNull(getKeyField());
    }

    /**
     * 是否有主键？
     */
    public boolean getHasPrimaryKey() {
        return Objects.nonNull(getKeyField());
    }

    /**
     * 获取版本字段
     */
    public FieldInfo getVersionField() {
        return fields.stream().filter(fieldInfo -> fieldInfo.isVersionFlag())
                .findFirst().orElse(null);
    }

    /**
     * 是否有版本字段？
     */
    public boolean getHasVersionField() {
        return Objects.nonNull(getVersionField());
    }

    /**
     * 获取逻辑删除字段
     */
    public FieldInfo getLogicDeleteField() {
        return fields.stream().filter(fieldInfo -> fieldInfo.isLogicDeleteFlag())
                .findFirst().orElse(null);
    }

    /**
     * 是否有逻辑删除字段？
     */
    public boolean getHasLogicDeleteField() {
        return Objects.nonNull(getLogicDeleteField());
    }

    /**
     * 获取创建时间字段
     */
    public FieldInfo getCreateTimeField() {
        return fields.stream().filter(fieldInfo -> fieldInfo.isCreateTimeFlag())
                .findFirst().orElse(null);
    }

    /**
     * 是否有创建时间字段？
     */
    public boolean getHasCreateTimeField() {
        return Objects.nonNull(getCreateTimeField());
    }

    /**
     * 获取创建人员字段
     */
    public FieldInfo getCreateUserField() {
        return fields.stream().filter(fieldInfo -> fieldInfo.isCreateUserFlag())
                .findFirst().orElse(null);
    }

    /**
     * 是否有创建人员字段？
     */
    public boolean getHasCreateUserField() {
        return Objects.nonNull(getCreateUserField());
    }

    /**
     * 获取修改时间字段
     */
    public FieldInfo getUpdateTimeField() {
        return fields.stream().filter(fieldInfo -> fieldInfo.isUpdateTimeFlag())
                .findFirst().orElse(null);
    }

    /**
     * 是否有修改时间字段？
     */
    public boolean getHasUpdateTimeField() {
        return Objects.nonNull(getUpdateTimeField());
    }

    /**
     * 获取修改人员字段
     */
    public FieldInfo getUpdateUserField() {
        return fields.stream().filter(fieldInfo -> fieldInfo.isUpdateUserFlag())
                .findFirst().orElse(null);
    }

    /**
     * 是否有修改人员字段？
     */
    public boolean getHasUpdateUserField() {
        return Objects.nonNull(getUpdateUserField());
    }

    /**
     * 获取上级id字段
     */
    public FieldInfo getParentField() {
        return fields.stream().filter(fieldInfo -> fieldInfo.getColumnName().equalsIgnoreCase("parent_id"))
                .findFirst().orElse(null);
    }

    /**
     * 是否有上级id字段？
     */
    public boolean getHasParentField() {
        return Objects.nonNull(getParentField());
    }

    /**
     * 是否支持tree？
     */
    public boolean isSupportTree() {
        return Objects.nonNull(getHasParentField());
    }

    /**
     * 获取排序字段
     */
    public FieldInfo getSortField() {
        return fields.stream().filter(fieldInfo -> fieldInfo.getColumnName().equalsIgnoreCase("sort"))
                .findFirst().orElse(null);
    }

    /**
     * 是否有排序字段？
     */
    public boolean getHasSortField() {
        return Objects.nonNull(getSortField());
    }

    /**
     * 获取默认排序字段，第一个 String 字段
     */
    public FieldInfo getDefaultSortField() {
        return fields.stream().filter(fieldInfo -> fieldInfo.getColumnType().getType().equalsIgnoreCase("String"))
                .findFirst().orElse(null);
    }

    /**
     * 是否有默认排序字段？
     */
    public boolean getHasDefaultSortField() {
        return Objects.nonNull(getDefaultSortField());
    }

    /**
     * 获取关键字查询字段，第一个 String 字段
     */
    public FieldInfo getKeywordsQueryField() {
        return fields.stream().filter(fieldInfo -> fieldInfo.getColumnType().getType().equalsIgnoreCase("String"))
                .findFirst().orElse(null);
    }

    /**
     * 是否有关键字查询字段？
     */
    public boolean getHasKeywordsQueryField() {
        return Objects.nonNull(getKeywordsQueryField());
    }

    /**
     * 获取描述字段，第一个 String 字段
     */
    public FieldInfo getDescriptionField() {
        return fields.stream().filter(fieldInfo -> fieldInfo.getColumnType().getType().equalsIgnoreCase("String"))
                .findFirst().orElse(null);
    }

    /**
     * 是否有描述字段？
     */
    public boolean getHasDescriptionField() {
        return Objects.nonNull(getDescriptionField());
    }

    /**
     * 获取唯一字段列表
     */
    public List<FieldInfo> getUniqueFields() {
        return fields.stream().filter(fieldInfo -> fieldInfo.isUnique())
                .toList();
    }

    /**
     * 是否有唯一字段
     */
    public boolean getHasUniqueFields() {
        return CollectionUtils.isNotEmpty(getUniqueFields());
    }

    /**
     * 获取必需字段列表
     */
    public List<FieldInfo> getRequireFields() {
        return fields.stream().filter(fieldInfo -> fieldInfo.isRequired())
                .toList();
    }

    /**
     * 是否有必需字段
     */
    public boolean getHasRequireFields() {
        return CollectionUtils.isNotEmpty(getRequireFields());
    }

    /**
     * 获取开关字段列表
     */
    public List<FieldInfo> getSwitchFields() {
        return fields.stream().filter(fieldInfo -> fieldInfo.getColumnType().getType().equalsIgnoreCase("Boolean"))
                .toList();
    }

    /**
     * 是否有开关字段
     */
    public boolean getHasSwitchFields() {
        return CollectionUtils.isNotEmpty(getSwitchFields());
    }

    /**
     * 是否有验证器？
     */
    public boolean getHasValidators() {
        return fields.stream().anyMatch(fieldInfo -> fieldInfo.getHasValidators());
    }

    /**
     * 验证导入类
     *
     * @return 类列表
     */
    public List<String> getValidatorImports() {
        List<String> imports = new ArrayList<>();
        fields.stream().forEach(fieldInfo -> {
            fieldInfo.getValidators().stream().forEach(validator -> {
                if (StringUtils.isNotBlank(validator.getImportClassName())
                        && !imports.contains(validator.getImportClassName())) {
                    imports.add(validator.getImportClassName());
                }
            });
        });
        return imports;
    }

    /**
     * 获取命名助手
     */
    public Map<String, Object> getNf() {
        Map<String, Object> map = new HashMap<>();
        NameConverter nameConverter = generatorConfig.getNameConverter();
        Arrays.stream(NameFormat.values()).forEach(nameFormat -> {
            try {
                String value = nameConverter.convert(this, nameFormat);
                map.put(nameConverter.camelName(nameFormat.name()), value);
            } catch (RuntimeException e) {
                // 忽略错误
            }
        });

        // Mapper方法命名规则：
        // * 返回列表的方法，使用复数形式，不用List，例如 queryDepartments
        // * 以 queryXXX() 开头的方法，返回 QueryVO
        // * 以 fetchXXX() 开头的方法，返回 DetailVO
        // * 以 selectXXX() 开头的方法，返回 Entity 或者 原始数据类型
        //
        // Service方法命名规则：
        // * 返回数据的方法都以 getXXX() 形式
        //

        map.put("swagger", NamingHelper.swagger(this));

        map.put("constantClass", NamingHelper.javaClass(this, "Constants", "constant", javaClassInfo -> {
            javaClassInfo.getExtra().put("requestUrl", NamingHelper.keyValue(
                    String.format("URL", NamingHelper.domainName(this).underline().upper()),
                    String.format("%s/%s", NamingHelper.moduleName(this).replace(".", "/"), NamingHelper.domainName(this).camel())));
            javaClassInfo.getExtra().put("permission", NamingHelper.keyValue(
                    String.format("PERMISSION", NamingHelper.domainName(this).underline().upper()),
                    String.format("%s:%s", NamingHelper.moduleName(this).replace(".", ":"), NamingHelper.domainName(this).camel())));
            javaClassInfo.getExtra().put("tag", NamingHelper.keyValue(
                    String.format("TAG", NamingHelper.domainName(this).underline().upper()),
                    String.format("%s/%s", this.getApifoxFolder(), this.getComment())));
        }));

        map.put("domainClass", NamingHelper.javaClass(this, "", "domain"));
        map.put("entityClass", NamingHelper.javaClass(this, "Entity", "entity"));
        map.put("validatorClass", NamingHelper.javaClass(this, "Validator", "validator"));

        map.put("mapperClass", NamingHelper.javaClass(this, "Mapper", "mapper", javaClassInfo -> {
            javaClassInfo.getExtra().put("queryMethod", String.format("query%s", NamingHelper.domainName(this).plural()));
            javaClassInfo.getExtra().put("fetchMethod", String.format("fetch%s", NamingHelper.domainName(this)));
            javaClassInfo.getExtra().put("createMethod", String.format("create%s", NamingHelper.domainName(this)));
            javaClassInfo.getExtra().put("updateMethod", String.format("update%s", NamingHelper.domainName(this)));
            javaClassInfo.getExtra().put("deleteMethod", String.format("delete%s", NamingHelper.domainName(this)));
        }));
        map.put("mapperXml", NamingHelper.resourceFile(this, "Mapper", "mapper", "xml"));

        map.put("queryRequestClass", NamingHelper.javaClass(this, "QueryDTO", "request"));
        map.put("createRequestClass", NamingHelper.javaClass(this, "CreateDTO", "request"));
        map.put("updateRequestClass", NamingHelper.javaClass(this, "UpdateDTO", "request"));

        map.put("queryResponseClass", NamingHelper.javaClass(this, "QueryVO", "response"));
        map.put("detailResponseClass", NamingHelper.javaClass(this, "DetailVO", "response"));
        map.put("treeResponseClass", NamingHelper.javaClass(this, "TreeVO", "response"));

        map.put("curdServiceClass", NamingHelper.javaClass(this, "CurdService", "service", javaClassInfo -> {
            javaClassInfo.getExtra().put("queryMethod", String.format("get%sList", NamingHelper.domainName(this).plural()));
            javaClassInfo.getExtra().put("fetchMethod", String.format("get%sDetail", NamingHelper.domainName(this)));
            javaClassInfo.getExtra().put("createMethod", String.format("create%s", NamingHelper.domainName(this)));
            javaClassInfo.getExtra().put("updateMethod", String.format("update%s", NamingHelper.domainName(this)));
            javaClassInfo.getExtra().put("deleteMethod", String.format("delete%s", NamingHelper.domainName(this)));
            javaClassInfo.getExtra().put("batchDeleteMethod", String.format("batchDelete%s", NamingHelper.domainName(this).plural()));
        }));
        map.put("curdControllerClass", NamingHelper.javaClass(this, "CurdController", "controller"));

        map.put("listServiceClass", NamingHelper.javaClass(this, "ListService", "service"));

        map.put("excelServiceClass", NamingHelper.javaClass(this, "ExcelService", "service", javaClassInfo -> {
            javaClassInfo.getExtra().put("importMethod", String.format("import%s", NamingHelper.domainName(this).plural()));
            javaClassInfo.getExtra().put("exportMethod", String.format("export%s", NamingHelper.domainName(this).plural()));
            javaClassInfo.getExtra().put("templateMethod", String.format("download%sTemplate", NamingHelper.domainName(this)));
        }));
        map.put("excelControllerClass", NamingHelper.javaClass(this, "ExcelController", "controller"));

        map.put("cacheServiceClass", NamingHelper.javaClass(this, "CacheService", "service", javaClassInfo -> {
            javaClassInfo.getExtra().put("listCache", NamingHelper.cache(this, "ListCache"));
            javaClassInfo.getExtra().put("mapCache", NamingHelper.cache(this, "MapCache"));
            javaClassInfo.getExtra().put("listMethod", String.format("get%sList", NamingHelper.domainName(this)));
            javaClassInfo.getExtra().put("mapMethod", String.format("get%sMap", NamingHelper.domainName(this)));
            javaClassInfo.getExtra().put("clearMethod", String.format("clear%sCache", NamingHelper.domainName(this)));

        }));
        map.put("cacheControllerClass", NamingHelper.javaClass(this, "CacheController", "controller"));

        map.put("assignServiceClass", NamingHelper.javaClass(this, "AssignService", "service"));
        map.put("assignControllerClass", NamingHelper.javaClass(this, "AssignController", "controller"));

        map.put("treeServiceClass", NamingHelper.javaClass(this, "TreeService", "service", javaClassInfo -> {
            javaClassInfo.getExtra().put("treeCache", NamingHelper.cache(this, "TreeCache"));
            javaClassInfo.getExtra().put("pathCache", NamingHelper.cache(this, "PathCache"));
            javaClassInfo.getExtra().put("keyCache", NamingHelper.cache(this, "SelfAndChildrenCache"));
            javaClassInfo.getExtra().put("treeMethod", String.format("get%sTree", NamingHelper.domainName(this)));
            javaClassInfo.getExtra().put("pathMethod", String.format("get%sPathMap", NamingHelper.domainName(this)));
            javaClassInfo.getExtra().put("keyMethod", String.format("get%sSelfAndChildren", NamingHelper.domainName(this)));
        }));
        map.put("treeControllerClass", NamingHelper.javaClass(this, "TreeController", "controller"));

        return map;
    }
}
